|
184 |
|
# UI | 03 | Playwright: взаимодействие с элементами
В данной статье будет представлен лишь обзор / поверхностная информация имеющихся во фреймворке методом для работы с элементами:
Поиск элемента
В данной главе представлены методы, с помощью которых можно найти элементы.
get_by_alt_text()
- Применение:
# регистронезависимый поиск
page.get_by_alt_text("расшифровка ls-вывода")
# регистрозависимый поиск
page.get_by_alt_text("расшифровка ls-вывода", exact=True)
Является аналогом для:
page.locator("//*[@alt='расшифровка ls-вывода']")
get_by_label()
- Применение:
# регистронезависимый поиск
page.get_by_label("some_label")
# регистрозависимый поиск
page.get_by_label("some_label", exact=True)
Является аналогом для:
page.locator('//*[contains(@aria-label, "some_label")]')
page.locator('//label[contains(text(), "some_label")]')
get_by_placeholder()
- Применение:
<input placeholder="местодержатель"/>
# регистронезависимый поиск
page.get_by_placeholder("стодерж")
# регистрозависимый поиск
page.get_by_placeholder("стодерж", exact=True)
Является аналогом для:
page.locator('//*[contains(@placeholder, "стодерж")]')
get_by_role()
Описание ролей описано тут.
get_by_role()
принимает далеко не все значения из этого списка, так как у функции имеется свой - более ограниченный - список.
Пример ролей:
button
-<button><button>
checkbox
-<input type=checkbox/>
heading
-<h1></h1>
img
-<img src=...>
link
-<a href=...></a>
["alert", "alertdialog", "application", "article", "banner", "blockquote", "button", "caption", "cell", "checkbox", "code", "columnheader", "combobox", "complementary", "contentinfo", "definition", "deletion", "dialog", "directory", "document", "emphasis", "feed", "figure", "form", "generic", "grid", "gridcell", "group", "heading", "img", "insertion", "link", "list", "listbox", "listitem", "log", "main", "marquee", "math", "menu", "menubar", "menuitem", "menuitemcheckbox", "menuitemradio", "meter", "navigation", "none", "note", "option", "paragraph", "presentation", "progressbar", "radio", "radiogroup", "region", "row", "rowgroup", "rowheader", "scrollbar", "search", "searchbox", "separator", "slider", "spinbutton", "status", "strong", "subscript", "superscript", "switch", "tab", "table", "tablist", "tabpanel", "term", "textbox", "time", "timer", "toolbar", "tooltip", "tree", "treegrid", "treeitem"]
Помимо самих ролей имеются ещё и дополнительные / необязательные параметры, позволяющие уточнить запрос:
checked
: typing.Optional[bool] = None,disabled
: typing.Optional[bool] = None,expanded
: typing.Optional[bool] = None,include_hidden
: typing.Optional[bool] = None,level
: typing.Optional[int] = None,name
: typing.Optional[typing.Union[str, typing.Pattern[str]]] = None,pressed
: typing.Optional[bool] = None,selected
: typing.Optional[bool] = None,exact
: typing.Optional[bool] = None
Пример применения:
#найти любой h* с текстом 'пример'
<h3>пример</h3>
page.get_by_role("heading", name="пример")
# регистронезависимый
page.get_by_role("heading", name=re.compile("пример", re.IGNORECASE))
get_by_text()
Делает, что написано в названии. Поддерживает regexp
, exact=True
(см. описание метода).
<div>Hello <span>world</span></div>
page.get_by_text("Hello world")
Из необычного => несмотря на то, что в примере выше оба слова вложены в разные элементы, совпадение всё равно будет обнаружено.
locator()
Ранее уже приводилось много примеров, но не упоминалось, что поддерживает разный синтаксис:
#xpath
page.locator('//p[text()="Ищу xpath"]')
#xpath=
page.locator('xpath=//p[text()="Ищу xpath"]')
#css
elem = page.locator('.c1')
print(f"всего элементов: {elem.count()}")
всего элементов: 16
#css=
page.locator('css=.c1')
Данная функция также может принимать на вход дополнительные параметры:
has_text
- ищет текст, содержащийся по частям или полностью в текущем и/или дочерних элементах, регистронезависимhas_not_text
- то же самое, но проверка осуществляется на отсутствие указанного текстаhas
- ищет локаторы (а не текст) внутри дочерних элементовhas_not
- проверяет отсутствие указанного локатора в дочерних элементах
Тем самым - помимо "прямого" использования xpath
, css
(с или без псевдолокаторов) - можно прокидывать дополнительные условия через данные аргументы. Но важно помнить, что эти методы могут использоваться только для проверки и никак не изменяют положение селектора.
Поэтому, если требуется, напр., кликнуть по какому-либо элементу, то требуется корректно указать его путь в первом аргументе (как показано в примерах выше).
has_text
Элементы class="c1" с текстом "прав не выполнение":
page.goto("https://g-oak.ru/post/Linux|Права|theory_permissions")
page.locator('css=.c1', has_text="прав не выполнение")
print(f"{elem.all_inner_texts()}")
['# нет прав не выполнение => rw-']
has_not_text
Элементы class="c1" без буквы "н":
page.goto("https://g-oak.ru/post/Linux|Права|theory_permissions")
elem = page.locator('css=.c1', has_not_text="н")
print(f"всего элементов: {elem.all_inner_texts()}")
всего элементов: [
'# проверяем текущие прва',
'# ожидаемо получаем ошибку',
'# сделать владельцем пользователя dao2',
'# сделать владельцем пользователя, из-под которого мы сейчас работаем'
]
has
Элемент li
, содержащий вложенный элемент <a>
# li содержит текст "а"
page.locator("//li", has=page.locator('text="a"'))
# li содержит локатор "а"
page.locator("//li", has=page.locator('a'))
# li содержит локатор "а" и текст "удаление"
page.locator("//li", has=page.locator('a'), has_text="Удаление")
has_not
# li не содержит локатор "div",
# но содержит текст "удаление"
page.locator("//li", has_not=page.locator('div'), has_text="Удаление")
query_selector()
Отдельно следует подчеркнуть, что согласно документации использование метода query_selector
нежелательно.
Если совпадений не найдено, метод возвращает null
. Чтобы ждать локатор, требуется дополнительно вызывать wait_for
.
По умолчанию может вернуть как одно, так и много элементов, но если использовать strict=True
, то - если найдено более одного совпадения - вернёт exception.
Работает как с разными видами локаторов (xpath, css):
page.query_selector('div#some-id')
page.query_selector('//div[@id, "some-id"]')
query_selector_all()
Если нет совпадений возвращает пустой список []
:
page.query_selector_all('div#some-id')
Проверка и ожидание состояния элемента
is_checked()
Применим только к radiobutton
или checkbox
. В противном случае выбрасывает exception
. Если в результате запроса находится несколько элементов, то проверяется только первый.
При применении strict
- в случае наличия более одного совпадения - выкинет exception
.
Можно выставить timeout
:
page.is_checked(
".checkbox",
strict=True,
timeout=15000
)
is_disabled()
Является противоположностью к is_enabled. Любой элемент считается enabled
, если он:
- не принадлежит к
<button>
,<select>
,<input>
или<textarea>
- и при этом у него нет свойства
disabled
Если в результате запроса находится несколько элементов, то проверяется только первый.
При применении strict
- в случае наличия более одного совпадения - выкинет exception
.
Можно выставить timeout
:
page.is_disabled(
".checkbox",
strict=True,
timeout=15000
)
is_editable()
Любой элемент считается is_editable, если он:
- является
enabled
- и при этом у него нет свойства
readonly
Если в результате запроса находится несколько элементов, то проверяется только первый.
При применении strict
- в случае наличия более одного совпадения - выкинет exception
.
Можно выставить timeout
:
page.is_editable(
".checkbox",
strict=True,
timeout=15000
)
is_enabled()
См. is_disabled
page.is_enabled(
".checkbox",
strict=True,
timeout=15000
)
is_hidden()
Является противоположностью к is_visible. Любой элемент считается hidden
, если он:
- имеет нулевой размер (является "пустым")
- имеет свойство
display:none
Важно: элементы с opacity:0
считаются видимыми
Если в результате запроса находится несколько элементов, то проверяется только первый.
При применении strict
- в случае наличия более одного совпадения - выкинет exception
.
Можно выставить timeout
:
page.is_hidden(
".checkbox",
strict=True,
timeout=15000
)
is_visible()
См. is_hidden
page.is_visible(
".checkbox",
strict=True,
timeout=15000
)
wait_for_selector()
Данный метод описывался в статье "# UI | 01 | Playwright: об ожиданиях
"
Обработка нажатий
Какие автоматически проверки используются при вызове того или иного метода "нажатий", подробно описано в документации.
clear()
Применим только к элементам типа <input>
, <textarea>
или [contenteditable]
. Проверят, что элемент visible
, editable
, enabled
, после чего очищает поле.
page.locator('//input[@id="current_page"]')
click()
Поддерживает большое количество действий:
- выбор нажатия кнопок мышек: левая, правая, колесо
- дополнительное нажатие клавиш клавиатуры
- передача координат для клика
- количество кликов
- таймаут
- псевдоклик
page.locator('//div[@id="previous_page"]').click()
См. документацию
drag_to()
Данный метод может использоваться для перемещения какого-либо элемента на странице по его локатору:
page.locator('перетаскиваемый').drag_to(page.locator('куда_бросить'));
То же самое можно осуществить следующим образом:
page.locator('перетаскиваемый').hover();
page.mouse.down();
page.locator('куда_бросить').hover();
page.mouse.up();
fill()
Вставка тексте. Применим к элементам типа: <input>
, <textarea>
или [contenteditable]
.
page.locator('//input[@id="current_page"]').fill("0")
См. документацию
input_value()
Возвращает значение value
для элементов типа: <input>
or <textarea>
, <select>
, *<label>
current_value = page.locator("...").input_value()
available_values = page.locator("...").input_values()
press()
Данный метод может применяться к разным объектам:
Locator
Page
Keyboard
Frame
ElementHandle
Здесь рассматривается применение только к локатору.
page.get_by_role("textbox").press("Backspace")
См. документацию
press_sequentially
В большинстве случаев нет необходимости использовать этот метод, так как существует аналогичный: locator.fill("hello")
# печать с задержкой
locator.press_sequentially("пример", delay=100)
См. документацию
tap()
То же самое, что click()
, поддерживает те же аргументы. Симулирует касание экрана. Чтобы пользоваться данным методом, контекст браузера должен иметь опцию hasTouch=True
.
См. документацию
Работа с клавиатурой
- keyboard.down()
- keyboard.up()
- keyboard.insert_text()
- keyboard.press()
- keyboard.release()
- keyboard.type()
Клавиши: F1 - F12, Digit0- Digit9, KeyA- KeyZ, Backquote, Minus, Equal, Backslash, Backspace, Tab, Delete, Escape, ArrowDown, End, Enter, Home, Insert, PageDown, PageUp, ArrowRight, ArrowUp
keyboard.down()
# ввести текст 523
page.keyboard.down("Digit5")
page.keyboard.down("Digit2")
page.keyboard.down("Digit3")
keyboard.up()
# ввести текст: aABb
page.keyboard.down("KeyA")
page.keyboard.down("Shift")
page.keyboard.down("KeyA")
page.keyboard.down("KeyB")
page.keyboard.up("Shift")
page.keyboard.down("KeyB")
keyboard.insert_text()
page.keyboard.insert_text("Так проще вставить текст")
См. документацию
keyboard.press()
Вместо этого методоа рекомендуется использовать locator.press()
.
# Ввести текст "Oa"
page.keyboard.press("a")
page.keyboard.press("ArrowLeft")
page.keyboard.press("Shift+O")
keyboard.type()
Имеются более предпочтительные аналоги: locator.fill()
, locator.press_sequentially()
page.keyboard.type("World", delay=100)
См. документацию
Работа с мышкой
- mouse.click()
- mouse.dblclick()
- mouse.down()
- mouse.move()
- mouse.up()
- mouse.wheel()
Пример:
page.mouse.click(200, 300)
page.mouse.click(x=400, y=500)
См. документацию
focus()
Устанавливает фокус на html-элементе, то есть, данный элемент будет по умолчанию принимать события клавиатуры и пр.
pagination_field = page.locator('//input[@id="current_page"]')
pagination_field.focus()
page.keyboard.press("a")
page.keyboard.press("ArrowLeft")
page.keyboard.press("Shift+O")
pagination_field.scroll_into_view_if_needed()
События клавиатуры отправляются в поле pagination_field
, так как фокус выставлен на нём.
Метод focus()
поддерживает параметр timeout
.
hover()
Данный метод наводит "курсор мышки" на указанный локатор. Имеет свою логику:
- выполняет определённые условия ожидания
- прокручивает страницу до элемента, елси необходимо
- использует
page.mouse
для изменения положения курсора
Поддерживает параметры:
- modifiers
- position
- timeout
- no_wait_after
- force
- trial
См. документацию
Обращение к элементам
all()
Сразу возвращает найденные элементы. Если требуется ожидание, надо применять его дополнительно. Данный элемент применяется только к объектам типа локатор и возвращает список всех найденных элементов. Напр.:
page.locator("/div").all()
page.get_by_role("header").all()
all_inner_texts()
Возвращает список с элементами типа текст. Говоря на языке html, обращается к node.innerText
, где под node
подразумевается какой-либо html
-элемент.
page.goto("http://g-oak.ru/post/Linux|Права|theory_permissions")
elem = page.get_by_role("link").all_inner_texts()
print(elem)
['Введение', 'Владелец, группа, Остальные',
'Права доступа к файлам и каталогам',
'Информация о файле в системе',
'Особенности чтения информации о директории']
Не стоит применять данный метод для проверки текста на соответствие, так как для такой цели имеется самостоятельная фнукция to_have_text().
all_text_contents()
Возвращает список с элементами типа текст. Говоря на языке html, обращается к node.textContent
, где под node
подразумевается какой-либо html
-элемент.
page.goto("http://g-oak.ru/post/Linux|Права|theory_permissions")
elem = page.get_by_role("link").all_text_contents()
print(elem)
['Введение', 'Владелец, группа, Остальные',
'Права доступа к файлам и каталогам',
'Информация о файле в системе',
'Особенности чтения информации о директории']
Не стоит применять данный метод для проверки текста на соответствие, так как для такой цели имеется самостоятельная фнукция to_have_text().
count()
Применим к элементам типа локатор, т. е. возвращаемый элемент должен быть именно объектом типа локатор (а не, напр., списком).
elem = page.locator("//li").count()
print(elem)
Не стоит применять данный метод для проверки на соответствие (ассерт-ов), так как для такой цели имеется самостоятельная фнукция to_have_count().
filter()
Имеет те же доп. параметры по работе с текстом, что и метод locator()
:
has_text
has_not_text
has
has_not
Данная функция может быть использована "в цепочке" для получения финального результата:
row_locator = page.locator("tr")
row_locator.filter(
has_text="text in column 1").filter(
has=page.get_by_role("button", name="column 2 button")
).screenshot()
first
Применяется к локаторам, возвращает первый подходящий элемент.
elem = page.locator("//li").first
Вернёт первый элемент списка.
get_attribute()
Позволяет получить значение атрибута. На вход подаётся название атрибута, значение которого требуется прочитать. Метод также поддерживает timeout
.
<h4 id="_4">Группа</h4>
elem = page.locator(
"//h4[text()='Группа']").get_attribute(
"id", timeout=15000)
_4
Не стоит применять данный метод для проверки на соответствие (ассерт-ов), так как для такой цели имеется самостоятельная фнукция to_have_attribute().
inner_html()
Говоря на языке html, обращается к element.inner_html
, где под element
подразумевается какой-либо html
-элемент.
- отображаем внутренние элементы для
ul
<ul>
<li><strong>владельцу файла</strong></li>
<li><strong>группе, к которой относится файл</strong></li>
<li><strong>остальным</strong></li>
</ul>
elem = page.locator(
'//*[contains(text(), "группе, к которой относится файл")]/../..').inner_html()
print(elem)
Внутренними html-элементами ul
являются:
<li><strong>владельцу файла</strong></li>
<li><strong>группе, к которой относится файл</strong></li>
<li><strong>остальным</strong></li>
inner_text()
Возвращает внутренний текст html-элемента
page.locator("//label[@id='index_title']").inner_text()
Имеет дополнительный параметр timeout
, возвращает объект типа str
.
last
Применяется к локаторам, возвращает последний элемент.
elem = page.locator("//li").last
text_content()
Возвращает элемент типа текст. Говоря на языке html, обращается к node.textContent
, где под node
подразумевается какой-либо html
-элемент.
Не стоит применять данный метод для проверки текста на соответствие, так как для такой цели имеется самостоятельная фнукция to_have_text().
Изменение состояния элемента
check()
Используется для активации чекбокса или радиокнопки. Если элемент уже выбран, то просто прекращается выполнение.
page.get_by_role("checkbox").check()
Поддерживает параметры:
- position
- timeout
- force
- no_wait_after
- trial
highlight()
Используется применительно к локаторам. Может быть полезен, напр., для дебага, так как выделяет на стринце текущий локатор, а также прописывает его путь.
Но стоит помнить, что применять его следует только для дебагинга и не использовать в "живом" коде:
Highlight the corresponding element(s) on the screen. Useful for debugging, don't commit the code that uses
locator.highlight().
select_option()
Выбор опции / опций внутри элемента типа <select>
.
См. документацию
select_text()
Позволяет выбрать текст - в смысле выделить весь текст - внутри элемента. Имеет собственную логику.
См. документацию
set_checked()
В чём-то схож с check()
. Используется для де-/активации чекбокса или радиокнопки.
page.get_by_role("checkbox").set_checked(True)
См. документацию
uncheck()
Противоположность методу check()
. Если радиокнопка или чекбокс уже деактивированы, то просто прекратит своё выполнение.
См. документацию
Ожидания vs expect
В Playwright
имеется огромное количество ожиданий - в плане - что мы ждём от поведения элемента.
from playwright.sync_api import sync_playwright, expect
expect(page.get_by_role("heading", name="пример")).to_be_visible()
Список методов:
- .to_be_attached()
- .to_be_checked()
- .to_be_disabled()
- .to_be_editable()
- .to_be_empty()
- .to_be_enabled()
- .to_be_focused()
- .to_be_hidden()
- .to_be_in_viewport()
- .to_be_visible()
У каждого из этих методов имеется противоположность вида: .not_*
(напр., .not_to_be_attached()
)
Также имеются методы проверок из серии to_have_*
и not_to_have_*
, напр.:
- .to_have_attribute()
- .to_have_class()
- .to_have_count()
- .to_have_css()
- .to_have_id()
- .to_have_js_property()
- .to_have_text()
- .to_have_value()
- .to_have_values()
См. документацию